package checker

import (
	"reflect"
	"kumachan/interpreter/lang/common/source"
	"kumachan/interpreter/compiler/checker/typsys"
	"kumachan/interpreter/compiler/checker/checked"
)


func (cc *checkContext) typeConsContext() TypeConsContext {
	return TypeConsContext {
		ModInfo:  cc.exprContext.ModuleInfo,
		TypeReg:  cc.exprContext.Registry.Types,
		AliasReg: cc.exprContext.Registry.Aliases,
		ParamVec: cc.exprContext.ParameterList,
	}
}
func (cc *checkContext) getType(t nominalType) typsys.Type {
	return t(cc.exprContext.Types)
}
func (cc *checkContext) describeType(t typsys.Type) string {
	return typsys.DescribeType(t, cc.inferring)
}
func (cc *checkContext) describeTypeOf(expr *checked.Expr) string {
	return cc.describeType(expr.Type)
}

func (cc *checkContext) assignType(to typsys.Type, from typsys.Type) *source.Error {
	var a = typsys.MakeAssignContext(cc.exprContext.ModName, cc.inferring)
	var ok, s = typsys.Assign(to, from, a)
	if ok {
		cc.inferring = s
		return nil
	} else {
		return source.MakeError(cc.location, E_NotAssignable {
			From: cc.describeType(from),
			To:   cc.describeType(to),
		})
	}
}
func (cc *checkContext) requireCertainOrInferredType(t typsys.Type) (typsys.Type, *source.Error) {
	var certain_t, ok = cc.getCertainType(t)
	if ok {
		return certain_t, nil
	} else {
		var inferred_t, ok = cc.getInferredType(t)
		if ok {
			return inferred_t, nil
		} else {
			return nil, source.MakeError(cc.location,
				E_ExplicitTypeRequired {})
		}
	}
}
func (cc *checkContext) getCertainType(t typsys.Type) (typsys.Type, bool) {
	if t == nil {
		return nil, false
	} else {
		if cc.inferring == nil {
			return t, true
		} else {
			return typsys.TypeOpGetCertainType(t, cc.inferring)
		}
	}
}
func (cc *checkContext) getInferredType(t typsys.Type) (typsys.Type, bool) {
	if t == nil {
		panic("something went wrong")
	} else {
		if cc.inferring == nil {
			panic("something went wrong")
		} else {
			return typsys.TypeOpGetInferredType(t, cc.inferring)
		}
	}
}
func (cc *checkContext) getExpectedCertainOrInferred(t typsys.Type) (typsys.Type, bool) {
	return typsys.TypeOpGetInferredExpectedType(t, cc.inferring)
}
func (cc *checkContext) getExpectedTuple(t typsys.Type) (typsys.Tuple, bool) {
	var get = func(t typsys.Type) (typsys.Tuple, bool) {
		return obtainIsoTuple(t, cc.exprContext.ModName)
	}
	var v, ok = cc.getExpectedTypeComponent(t, reflect.ValueOf(get))
	if ok {
		return v.(typsys.Tuple), true
	} else {
		return typsys.Tuple {}, false
	}
}
func (cc *checkContext) getExpectedRecord(t typsys.Type) (typsys.Record, bool) {
	var get = func(t typsys.Type) (typsys.Record, bool) {
		return obtainIsoRecord(t, cc.exprContext.ModName)
	}
	var v, ok = cc.getExpectedTypeComponent(t, reflect.ValueOf(get))
	if ok {
		return v.(typsys.Record), true
	} else {
		return typsys.Record {}, false
	}
}
func (cc *checkContext) getExpectedListItemType(t typsys.Type) (typsys.Type, bool) {
	var get func(typsys.Type) (typsys.Type, bool)
	get = func(t typsys.Type) (typsys.Type, bool) {
		var item_t, ok = coreList_(t)
		if ok {
			return item_t, true
		} else {
			var inner, box, is_box = typsys.Unbox(t, cc.exprContext.ModName)
			if is_box && box.BoxKind == typsys.Isomorphic {
				return get(inner)
			} else {
				return nil, false
			}
		}
	}
	var v, ok = cc.getExpectedTypeComponent(t, reflect.ValueOf(coreList_))
	if ok {
		return v.(typsys.Type), true
	} else {
		return nil, false
	}
}
func (cc *checkContext) getExpectedTypeComponent(t typsys.Type, get_ reflect.Value) (interface{}, bool) {
	var get = func(t typsys.Type) (interface{}, bool) {
		if t == nil {
			return nil, false
		}
		var ret = get_.Call([] reflect.Value { reflect.ValueOf(t) })
		var v, ok = ret[0].Interface(), ret[1].Interface()
		if ok.(bool) {
			return v, true
		} else {
			return nil, false
		}
	}
	var v, ok = get(t)
	if ok {
		return v, true
	} else {
		var t_, ok = typsys.TypeOpGetInferredExpectedType(t, cc.inferring)
		if ok {
			return get(t_)
		} else {
			return nil, false
		}
	}
}
func (cc *checkContext) getExpectedLambda(t typsys.Type, loc source.Location) (typsys.Lambda, *source.Error) {
	var get = func(t typsys.Type) (typsys.Lambda, bool) {
		return obtainIsoLambda(t, cc.exprContext.ModName)
	}
	var io, ok = get(t)
	if ok {
		var s = cc.inferring
		var in, ok = typsys.TypeOpGetInferredLambdaInputType(io.Input, s)
		if !(ok) { return typsys.Lambda {}, source.MakeError(loc,
			E_ExplicitTypeRequired {}) }
		var out = io.Output
		return typsys.Lambda {
			Input:  in,
			Output: out,
		}, nil
	} else {
		var t_, ok = typsys.TypeOpGetInferredExpectedType(t, cc.inferring)
		if ok {
			var io, ok = get(t_)
			if ok {
				return io, nil
			}
		}
		if t == nil {
			return typsys.Lambda {}, source.MakeError(loc,
				E_ExplicitTypeRequired {})
		} else {
			return typsys.Lambda {}, source.MakeError(loc,
				E_LambdaAssignedToIncompatible {
					TypeName: typsys.DescribeType(t, cc.inferring),
				})
		}
	}
}

func (cc *checkContext) unboxEnum(expr *checked.Expr) (*typsys.Enum, *typsys.TypeDef, ([] typsys.Type), bool) {
	return cc.unboxEnumFromType(expr.Type)
}
func (cc *checkContext) unboxEnumFromType(t typsys.Type) (*typsys.Enum, *typsys.TypeDef, ([] typsys.Type), bool) {
	return unboxEnum(t, cc.exprContext.ModName)
}
func (cc *checkContext) unboxRecord(expr *checked.Expr) (typsys.Record, bool) {
	return cc.unboxRecordFromType(expr.Type)
}
func (cc *checkContext) unboxRecordFromType(t typsys.Type) (typsys.Record, bool) {
	return unboxRecord(t, cc.exprContext.ModName)
}
func (cc *checkContext) unboxLambda(expr *checked.Expr) (typsys.Lambda, bool) {
	return cc.unboxLambdaFromType(expr.Type)
}
func (cc *checkContext) unboxLambdaFromType(t typsys.Type) (typsys.Lambda, bool) {
	return unboxLambda(t, cc.exprContext.ModName)
}
func (cc *checkContext) unboxInteriorReferableRecord(expr *checked.Expr) (typsys.Record, checked.InteriorRefOperand, typsys.Type, bool) {
	var record, ok = cc.unboxRecord(expr)
	if ok {
		var base_t = &typsys.NestedType { Content: record }
		return record, checked.RO_Record, base_t, true
	} else {
		var base_t, field_t, ok = coreProjRef_(expr.Type)
		if ok {
			var record, ok = cc.unboxRecordFromType(field_t)
			if ok {
				return record, checked.RO_ProjRef, base_t, true
			}
		}
		return typsys.Record {}, -1, nil, false
	}
}
func (cc *checkContext) unboxInteriorReferableEnum(expr *checked.Expr) (*typsys.Enum, ([] typsys.Type), checked.InteriorRefOperand, typsys.Type, bool) {
	var enum, enum_def, enum_args, ok = cc.unboxEnum(expr)
	if ok {
		var base_t = &typsys.NestedType { Content:
		typsys.Ref {
			Def:  enum_def,
			Args: enum_args,
		} }
		return enum, enum_args, checked.RO_Enum, base_t, true
	} else {
		{ var base_t, field_t, ok = coreProjRef_(expr.Type)
		if ok {
			var enum, _, enum_args, ok = cc.unboxEnumFromType(field_t)
			if ok {
				return enum, enum_args, checked.RO_ProjRef, base_t, true
			}
		} }
		{ var base_t, case_t, ok = coreCaseRef_(expr.Type)
		if ok {
			var enum, _, enum_args, ok = cc.unboxEnumFromType(case_t)
			if ok {
				return enum, enum_args, checked.RO_CaseRef, base_t, true
			}
		}}
		return nil, nil, -1, nil, false
	}
}


